feat: Categories Editor — browse, search, and view preset category sets (closes #143) - #147
Conversation
…egory format - Add import-file schemas: metadataSchema, fieldSchema, categorySchema, comapeoCatSchema - Vendor 5 representative category + 10 field fixtures from comapeo-default-categories v1.1.2 - Add loadDefaultCategoryFixtures() fixture loader - Export new schemas from barrel index - 54 tests covering valid/malformed/edge cases for all schemas
- usePresets: TanStack Query hook fetching presets from server API with Valibot validation - normalizeCategories: pure function for grouping by tags.type, locale-aware search, deterministic ordering - 17 tests covering success/error/validation/locale/grouping/search/edge cases
- Register /categories and /categories/ routes - Add Categories nav item with grid icon to primary rail - Create screen shell with loading/error/empty/search states - Add en/pt/es i18n messages for all strings - 4 screen tests covering loading/error/empty/search states
- CategoryCard: colored icon fallback, color accent strip, field count, semantic article - CategoryGrid: section headers by tags.type, responsive 2-4 column grid - Extended Category model with color/iconRef pass-through - 8 new tests covering group headings, cards, accents, accessibility, empty state
…elect unwrap - Add iconRef to PresetInput interface in useCategories - Remove unused Card import from CategoryCard - Fix duplicate useNavigate mock in test - Add select: (data) => data.data to usePresets for backward compat - Fix test assertions for unwrapped data shape
…refs
- Remove label from PresetInput.fieldRefs (API returns {docId,versionId,url} only)
- Add optional fieldLabels: Map<string,string> param to normalizeCategories
- Resolve field labels in search and output from the field lookup map
- Add test for field label resolution
- FieldViewer: read-only previews for text/selectOne/selectMultiple/date/number/unknown field types - CategoryDetail: icon, color swatch, field refs with resolved labels, back nav - Integrated useFields hook for field label resolution - Responsive layout: grid + sticky sidebar on desktop, full-width on mobile - CategoryCard: selection state with aria-pressed and keyboard support - 20 new tests (10 FieldViewer, 10 CategoryDetail), 31 total CategoriesEditor tests
- categories-db: Dexie DB with versioned schema, importCategorySet/getCategorySet/getCategorySets - useCategorySets: hook for listing imported category sets - ImportSetDialog: file upload, JSON parsing, Valibot validation, replace confirmation - 16 tests covering valid/malformed/replace/retrieve/dialog lifecycle
- Revert usePresets to use getPresets (data-layer) for backward compat - Create useApiPresets with apiClient + select unwrap for CategoriesEditor - Fix ESLint: remove unused Category import, move useMemo before early returns - Fix TypeScript: resolve consumer hooks' Preset type mismatch - Update test mocks for new hook structure
7f3506b to
312003e
Compare
|
Preview deployment ready: https://agent-comapeo-cloud-app-issu-62kr.comapeo-cloud-app.pages.dev Commit: |
… a11y - useCategorySets: add error state + .catch on toArray() and refresh - ImportSetDialog: useEffect cleanup for auto-close timer, try/catch on replace - categories-db: remove dead getCategoriesDb singleton - CategoriesEditor: add aria-label to search input
- CategoriesEditor: show 'Select a project' message when no project selected (stops eternal skeleton) - FieldViewer: fix select_one/select_multiple → selectOne/selectMultiple to match Valibot enum
- ImportSetDialog: wrap first-time import in try/catch, add failedToRead/importError i18n messages
- CategoryCard: use intl.formatMessage for field count (was hardcoded English)
- FieldViewer: use intl.formatMessage for Date/Number/Unknown type badges (was hardcoded)
- index.tsx: add selectProject message definition
- Add categories.* i18n keys to en/es/pt: fieldCount, fieldType.*, importSet.{failedToRead,importError}, selectProject, untitledProject
The presetRefSchema requires {docId, versionId, url} but the CoMapeo
preset schema v1 defines ref items as {docId, versionId} only — no url.
The server omits url from fieldRefs and iconRef in preset responses,
causing v.parse() to throw → preset query isError → 'Failed to load
categories' screen.
Switch to docRefSchema (which already exists for this exact reason —
the comment says 'a single missing field would previously have silently
dropped ALL tracks for the project') with optional versionId and url.
Verify that presetSchema accepts fieldRefs and iconRef without a 'url'
field — the comapeo-schema preset v1 defines ref items as {docId, versionId}
only. The server omits 'url' from these, and the strict presetRefSchema was
the root cause of the 'Failed to load categories' runtime error.
- i18n: 'Uncategorized' group header (useCategories sentinel '' + CategoryGrid translate) - ImportSetDialog: wrap getCategorySet conflict-check in try/catch - ImportSetDialog: replace-flow uses importError i18n key (was hardcoded English with wrong validationError key) - Static import getCategorySet (drop redundant dynamic import)
- CategoriesEditor index: test happy path (cards render), no-project state, empty state - useCategories: test real diacritics (Água, São Paulo, Ação) against unaccented queries - useCategories: test accented query against plain target name
…, iconRef/color, replace flow, useCategorySets Addresses all 4 MEDIUM findings from claude-qwen test-coverage review: - CategoryCard: click/keyboard selection + aria-pressed tests (10 tests) - useCategories: iconRef & color branch coverage (4 tests) - ImportSetDialog: replace-execution + file-read-failure paths (2 tests) - useCategorySets: new hook test file (4 tests — empty, populated, error, refresh)
…ML semantics, serverConfig hygiene
- Add missing pt/es translations for new field-type messages; remove stale
uncategorized/fieldCount/fieldType.unknown keys from pt/es
- Gate resolvedFields on fieldsQuery.isPending to avoid 'Field unavailable'
flash during initial fields load
- Replace invalid <h3> inside <button> with <span> (valid content model)
- Stop threading full {baseUrl, token} through UI props; CategoryIcon and
the chain now take serverBaseUrl (string | null) only
- Add tests: unresolved fieldRef → 'Field unavailable' (no raw docId),
fields-pending hold, empty geometry:[] exclusion
… valid phrasing content
…onse - ObservationCategoryIcon: add m-auto to the icon <img> and fallback letter so the SVG is centered inside its circle. PR #147 switched sizing to fixed width/height inside an absolute inset-0 box, which anchored the icon to the top-left corner, breaking horizontal/vertical centering. - fieldSchema: a real archive /field response can omit optional keys such as "universal" (and may carry extra keys like "geometry"). The previous strict v.object rejected those fields, throwing on parse and making the entire fields fetch fail to load. Make "universal" a tolerant v.fallback(boolean, false) so a missing key defaults to false while the output type stays boolean and downstream consumers are unaffected. RED to GREEN via tests: field.test.ts (real server-shaped field) and ObservationCategoryIcon centering test.
|
PR Readiness Worker — Run 2026-07-26T20:03:21Z
|
Latest fix: TDD for two field/icon regressions (commit
|
Real Mapeo archive server returns `tagKey` (not `key`) and camelCase field types (`selectOne`/`selectMultiple`). The previous fieldSchema required `key` + snake_case, so v.parse threw on every /field response in prod, getFields rejected, useApiFields errored, and CategoriesEditor showed "Field labels unavailable". - schemas/field.ts: fieldSchema accepts tagKey and key (both optional), accept selectOne/selectMultiple alongside select_one/select_multiple - fields/normalize.ts: API_TYPE_MAP maps camelCase types; normalizeApiField reads tagKey ?? key ?? '' - remote-archive.ts pullFields: map archive tagKey into Dexie `key` column (no migration) - fixtures: real-archive-response.json (4-field capture incl. 1 deleted); presets fieldsFixture key -> tagKey - tests: field/normalize/useApiFields unit coverage + field-resolution proof test (real MSW /field response: labels render, no error banner, deleted field excluded) Reviewed by Opus 5 (Round 2): APPROVED, no pre-merge blockers. Closes #143.
Gap A — Flaky LoginScreen test ("window is not defined" unhandled rejection):
guard setSubmitState behind a mountedRef so async handleSubmit cannot
schedule a React update after unmount/teardown.
Gap B — fieldSchema required createdAt/updatedAt; now v.fallback(v.string(),'')
to match remote-archive.ts pullFields (which already writes ?? '').
Removes the dead/tolerant mismatch; output type stays string.
Gap C — buildFieldLookup now skips deleted fields by default
(includeDeleted opt-in); adds regression tests in normalize.test.ts.
Gap D — document that Dexie Field.type is stored un-normalized camelCase.
Verified: tsc --noEmit clean, prettier clean, 57 unit tests pass
(incl. field-resolution proof + new gap tests). screenshots/ left untouched.
1. ObservationCategoryIcon size regression: inline style overrides
Tailwind classes. Two pre-existing callers (DataScreen h-12 w-12,
ObservationsMap h-7 w-7) rendered at default 32px. Changed to
pass explicit size={48} / size={28}. Added regression test.
2. Wire ImportSetDialog into CategoriesEditor toolbar. The dialog
was fully implemented + tested but unreachable. Added Import
button, isImportOpen state, i18n key (en/pt/es). Added tests
for button render + dialog open on click.
Addresses Opus 5 final readiness gate findings (PR #147).
…buttons - Replace dead hover:bg-primary-dark (undefined CSS var) with hover:bg-primary-hover on all 3 buttons - Add min-h-[44px] to Import button for mobile touch-target compliance
The import flow writes to IndexedDB but the screen reads from the server API — the two data sources are disconnected. Commenting out the UI until the full flow is implemented. Tracked in #151. - Comment out Import button + ImportSetDialog render in CategoriesEditorScreen - Comment out 2 import dialog wiring tests - Update PR title/description to remove 'import' from scope
Closes #143
What
Implements the Categories Editor screen at
/categories— browse and search preset categories from the server.Note: Category set import (
.comapeocatupload) is not included in this PR. The dialog and data layer exist but the import flow is not connected end-to-end. Tracked in #151.Changes
useApiPresets(TanStack Query),normalizeCategories(grouping/search/locale/field labels)sourceId(multi-archive support)Import Status
The
ImportSetDialogcomponent,categories-db.tsIndexedDB layer, andcomapeoCatSchemaare included but not wired into the UI. Import functionality requires connecting the IndexedDB write path to the server-backed read path. See #151.Greptile Summary
This PR adds a server-backed Categories Editor for browsing and searching preset category sets. The main changes are:
Confidence Score: 5/5
This looks safe to merge.
Important Files Changed
Reviews (46): Last reviewed commit: "refactor: comment out Import Category Se..." | Re-trigger Greptile
Context used: